// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Linq; using JetBrains.Annotations; using LargoCommon.Abstract; using LargoCommon.Localization; namespace LargoCommon.Music { /// /// Musical Block Model. /// [Serializable] public sealed class MelodicModel : AbstractModel, ICloneable { #region Fields /// /// The melodic motives /// private IEnumerable melodicMotives; /// /// The used melodic motives /// private Dictionary usedMelodicMotives; #endregion #region Constructors /// /// Initializes a new instance of the class. /// public MelodicModel() { this.MelodicMotives = new List(); } #endregion #region Properties /// /// Gets or sets the harmonic order. /// /// /// The harmonic order. /// public byte HarmonicOrder { get; set; } /// /// Gets or sets the melodic motives. /// /// /// The melodic motives. /// public IEnumerable MelodicMotives { get { Contract.Ensures(Contract.Result>() != null); if (this.melodicMotives == null) { throw new InvalidOperationException("Melodic motives are null."); } return this.melodicMotives; } set => this.melodicMotives = value ?? throw new ArgumentException(LocalizedMusic.String("Argument cannot be empty."), nameof(value)); } /// /// Gets the first melodic bar. /// /// Property description. [UsedImplicitly] public int FirstMelodicBar { get { var barNumber = (from c in this.BlockChanges.Changes where c.IsMelodicalNature orderby c.BarNumber select c.BarNumber).FirstOrDefault(); return barNumber; } } #endregion #region Static factory methods /// /// Extracts the musical block model. /// /// The musical block. /// /// Returns value. /// [UsedImplicitly] public static MelodicModel GetNewModel(MusicalBlock musicalBlock) { musicalBlock.Header.NumberOfLines = (byte)musicalBlock.Strip.Lines.Count; var model = GetNewModel(musicalBlock.Header.Name, musicalBlock); model.Number = musicalBlock.Header.Number; model.SourceMusicalBlock = musicalBlock; model.Header = musicalBlock.Header; ProcessLogger.Singleton.SendLogEvent(null, LocalizedMusic.String("Analyzing musical lines..."), 0); return model; } /// /// Gets the new musical model. /// /// Name of the model. /// The musical block. /// /// Returns value. /// /// Null Exception. public static MelodicModel GetNewModel(string modelName, MusicalBlock musicalBlock) { Contract.Requires(musicalBlock != null); var model = new MelodicModel { Name = modelName, IsSelected = false }; if (model == null) { throw new ArgumentNullException(nameof(modelName)); } model.Header = musicalBlock.Header; model.SourceMusicalBlock = musicalBlock; return model; } #endregion #region Public methods /// /// Gets the melodic motive. /// /// The number. /// Returns value. [UsedImplicitly] public MelodicMotive GetMelodicMotive(int number) { var cnt = this.MelodicMotives.Count(); if (cnt == 0) { return null; } var localNumber = number; if (localNumber > cnt) { checked { localNumber = ((number - 1) % cnt) + 1; } } //// MelodicMotive motive = this.MelodicMotives.ElementAt(localNumber); var motive = (from m in this.MelodicMotives where m.Number == localNumber select m).FirstOrDefault(); return motive; } /// /// Adds the motive. /// /// The motive. public void AddMotive(MelodicMotive motive) { Contract.Requires(motive != null); //// motive.Core = this; ((List)this.MelodicMotives).Add(motive); } #endregion /// /// Appends the melodic motives. /// Main algorithm to determine motivic classes and their instances /// /// The item groups. public void AppendMelodicMotives(List itemGroups) { if (this.MelodicMotives == null) { return; } var lastMelodicIdentifier = string.Empty; //// All motivic items ordered by length (descending) and identifier var melodicItemGroups = (from ig in itemGroups orderby ig.Length descending, ig.MelodicIdentifier select ig).ToList(); MelodicMotive melodicMotive = null; //// var numberWithinLength = 0; var lastLength = 0; foreach (var itemGroup in melodicItemGroups) { if (itemGroup.MusicalLine.FirstStatus.LineType != MusicalLineType.Melodic) { continue; } //// if (itemGroup.Length != lastLength) { numberWithinLength = 0; lastLength = itemGroup.Length; } var ident = itemGroup.MelodicIdentifier; if (ident != lastMelodicIdentifier) { //// Step to next new motive melodicMotive = itemGroup.MelodicMotive(0, string.Empty); this.AddMotive(melodicMotive); lastMelodicIdentifier = ident; } if (melodicMotive != null) { melodicMotive.Occurrence++; var area = itemGroup.GetArea(); this.SourceMusicalBlock.Body.MarkMelodicMotive(melodicMotive, area); } } this.CompleteMotives(); } /// /// Completes the motives. /// public void CompleteMotives() { var melodicMotiveNumber = 0; var orderedMotives = (from m in this.MelodicMotives orderby m.Occurrence descending, m.Length descending select m).ToList(); foreach (var motive in orderedMotives) { melodicMotiveNumber++; //// string motiveName = string.Format(CultureInfo.InvariantCulture,"T{0}/R{1}", ("0" + musicalTrack.LineIndex.ToString(CultureInfo.CurrentCulture)).Right(2), ("0000" + this.MelodicAnalyzer.RhythmicMotiveNumber.ToString(CultureInfo.CurrentCulture)).Right(4)); //// var motiveName = string.Format(CultureInfo.InvariantCulture, "R{0}", ("0000" + rhythmicMotiveNumber.ToString(CultureInfo.CurrentCulture)).Right(4)); var motiveName = MusicalProperties.GetMotiveName(string.Empty, melodicMotiveNumber, motive.Length); //// rhythmicMotiveNumber motive.Number = melodicMotiveNumber; motive.Name = motiveName; } } #region Unique motives /// /// Get UniqueTMelodicMotive. /// /// Unique Identifier. /// Returns value. [UsedImplicitly] public MelodicMotive GetUniqueMelodicMotive(string uniqueIdentifier) { if (this.usedMelodicMotives == null) { this.usedMelodicMotives = new Dictionary(); } var tmm = this.usedMelodicMotives.ContainsKey(uniqueIdentifier) ? this.usedMelodicMotives[uniqueIdentifier] : null; if (tmm != null) { return tmm; } //// Lock needed here var melodicMotiveList = this.MelodicMotives; foreach (var melodicMotive in melodicMotiveList.Where(melodicMotive => melodicMotive != null && string.Compare(melodicMotive.UniqueIdentifier, uniqueIdentifier, StringComparison.Ordinal) == 0)) { this.usedMelodicMotives[uniqueIdentifier] = melodicMotive; return melodicMotive; //// Avoid multiple or conditional return statements. } return null; } #endregion /// Makes a deep copy of the BlockModel object. /// Returns object. public object Clone() { var model = new MelodicModel { Name = this.Name, Number = this.Number, //// Core = this.Core, Header = (MusicalHeader)this.Header.Clone(), SourceMusicalBlock = this.SourceMusicalBlock }; return model; } #region Material Extractor /// /// Extract melodic material. /// /// /// Returns value. /// public MelodicMaterial ExtractMelodicMaterial() { //// var dcm = DataBridgeMaterial.GetMaterialContext; var tmm = new MelodicMaterial { Name = this.Name }; //// , CoreId = melodicCore.TMelodicCore.Id //// dcm.AddToTMelodicMaterial(tmm); var list = new Collection(); foreach (var structure in from motive in this.MelodicMotives from structure in motive.MelodicStructures let sc = structure.GetStructuralCode where !string.IsNullOrEmpty(sc) select structure) { list.Add(structure); } var groupList = (from ms in list group ms by ms.GetStructuralCode into g select g).ToList(); groupList.ForEach(g => { var s = g.FirstOrDefault(); if (s == null) { return; } var ms = s; //// Clone? ms.Occurrence = g.Count(); tmm.Structures.Add(ms); }); //// dcm.SaveChanges(); return tmm; } #endregion } }